/*
 *
 *  Connection Manager
 *
 *  Copyright (C) 2007-2009  Intel Corporation. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "connman.h"

#define	_DBG_STORAGE(fmt, arg...)	DBG(DBG_STORAGE, fmt, ## arg)

static GSList *storage_list = NULL;

static gint compare_priority(gconstpointer a, gconstpointer b)
{
	const struct connman_storage *storage1 = a;
	const struct connman_storage *storage2 = b;

	return storage2->priority - storage1->priority;
}

/**
 * connman_storage_register:
 * @storage: storage module
 *
 * Register a new storage module
 *
 * Returns: %0 on success
 */
int connman_storage_register(struct connman_storage *storage)
{
	_DBG_STORAGE("storage %p name %s", storage, storage->name);

	storage_list = g_slist_insert_sorted(storage_list, storage,
							compare_priority);

	return 0;
}

/**
 * connman_storage_unregister:
 * @storage: storage module
 *
 * Remove a previously registered storage module
 */
void connman_storage_unregister(struct connman_storage *storage)
{
	_DBG_STORAGE("storage %p name %s", storage, storage->name);

	storage_list = g_slist_remove(storage_list, storage);
}

void __connman_storage_set_encrypted_value(GKeyFile *keyfile,
    const char *section, const char *key, const char *value)
{
        char *cryptvalue = __connman_crypto_encrypt_keyvalue(key, value);
        g_key_file_set_string(keyfile, section, key, cryptvalue);
        g_free(cryptvalue);
}

char *__connman_storage_get_encrypted_value(GKeyFile *keyfile,
    const char *section, const char *key)
{
        char *ciphertext, *plaintext;

        ciphertext = g_key_file_get_string(keyfile, section, key, NULL);
        if (ciphertext == NULL)
                return NULL;

        plaintext = __connman_crypto_decrypt_keyvalue(key, ciphertext);
        g_free(ciphertext);

        return plaintext;
}

static const char *__getpath(const struct connman_storage_ident *ident)
{
	static char path[80];

	if (ident->user != NULL) {
		/* TODO(sleffler) worth using getpwnam & co? */
		/* TODO(sleffler) at least use #define */
		snprintf(path, sizeof(path), STORAGE_HOMEDIR "/%s.profile",
		    ident->user, ident->ident);
	} else {
		snprintf(path, sizeof(path), STORAGEDIR "/%s.profile",
		    ident->ident);
	}
	return path;
}

static char *getpath(const struct connman_storage_ident *ident)
{
	char *dir, *path;
	struct stat sb;

	if (ident->user != NULL)
		dir = g_strdup_printf(STORAGE_HOMEDIR, ident->user);
	else
		dir = g_strdup(STORAGEDIR);
	/* container directory must be root-owned and unwritable */
	if (lstat(dir, &sb) < 0) {
		connman_error("%s: cannot lstat: %s", dir, strerror(errno));
		goto bad;
	}
	if (!S_ISDIR(sb.st_mode)) {
		connman_error("%s: not a directory", dir);
		goto bad;
	}
	if ((sb.st_mode & (S_IWGRP|S_IWOTH)) != 0) {
		connman_error("%s: bad permissions 0%o", dir, sb.st_mode);
		goto bad;
	}
	if (sb.st_uid != geteuid()) {
		connman_error("%s: wrong owner, uid %d", dir, sb.st_uid);
		goto bad;
	}

	path = g_strdup_printf("%s/%s.profile", dir, ident->ident);
	g_free(dir);

	return path;
bad:
	g_free(dir);
	return NULL;
}

gboolean __connman_storage_exists(const struct connman_storage_ident *ident)
{
	gchar *pathname, *data = NULL;
	gboolean result;
	gsize length;

	_DBG_STORAGE("ident %s", __getpath(ident));

	pathname = getpath(ident);
	if (pathname == NULL)
		return FALSE;

	result = g_file_get_contents(pathname, &data, &length, NULL);

	g_free(pathname);
	g_free(data);

	return result;
}

GKeyFile *__connman_storage_open(const struct connman_storage_ident *ident)
{
	GKeyFile *keyfile;
	gchar *pathname, *data = NULL;
	gboolean result;
	gsize length;

	_DBG_STORAGE("ident %s", __getpath(ident));

	pathname = getpath(ident);
	if (pathname == NULL)
		return NULL;

	result = g_file_get_contents(pathname, &data, &length, NULL);

	g_free(pathname);

	keyfile = g_key_file_new();

	if (result == FALSE)
		goto done;

	if (length > 0)
		g_key_file_load_from_data(keyfile, data, length, 0, NULL);

	g_free(data);

done:
	_DBG_STORAGE("keyfile %p", keyfile);

	return keyfile;
}

void __connman_storage_close(const struct connman_storage_ident *ident,
					GKeyFile *keyfile, gboolean save)
{
	gchar *pathname, *data = NULL;
	gsize length = 0;

	_DBG_STORAGE("ident %s keyfile %p save %d", __getpath(ident),
	    keyfile, save);

	if (save == FALSE) {
		g_key_file_free(keyfile);
		return;
	}

	pathname = getpath(ident);
	if (pathname == NULL)
		return;

	data = g_key_file_to_data(keyfile, &length, NULL);

	if (g_file_set_contents(pathname, data, length, NULL) == FALSE)
		connman_error("%s: failed to store data for %s:%s",
		    __func__, ident->user, ident->ident);

	g_free(data);

	g_free(pathname);

	g_key_file_free(keyfile);
}

void __connman_storage_delete(const struct connman_storage_ident *ident)
{
	gchar *pathname;

	_DBG_STORAGE("ident %s", __getpath(ident));

	pathname = getpath(ident);
	if (pathname == NULL)
		return;

	if (unlink(pathname) < 0)
		connman_error("Failed to remove %s", pathname);

	g_free(pathname);
}

int __connman_storage_init_profile(void)
{
	GSList *list;

	_DBG_STORAGE("");

	for (list = storage_list; list; list = list->next) {
		struct connman_storage *storage = list->data;

		if (storage->profile_init) {
			if (storage->profile_init() == 0)
				return 0;
		}
	}

	return -ENOENT;
}

int __connman_storage_load_profile(struct connman_profile *profile)
{
	GSList *list;

	_DBG_STORAGE("profile %p", profile);

	for (list = storage_list; list; list = list->next) {
		struct connman_storage *storage = list->data;

		if (storage->profile_load) {
			if (storage->profile_load(profile) == 0)
				return 0;
		}
	}

	return -ENOENT;
}

int __connman_storage_save_profile(struct connman_profile *profile)
{
	GSList *list;

	_DBG_STORAGE("profile %p", profile);

	for (list = storage_list; list; list = list->next) {
		struct connman_storage *storage = list->data;

		if (storage->profile_save) {
			if (storage->profile_save(profile) == 0)
				return 0;
		}
	}

	return -ENOENT;
}

/*
 * Load/Save object support.  Walk the list of registered
 * storage implementors until we find one that has a handler
 * for the object type.  The storage implementor template
 * has per-object types (apparently) but these are not used
 * at the moment.
 */
#define	DOLOAD(func, arg, profile_ident) do {				\
	int err;							\
	GKeyFile *keyfile = __connman_storage_open(profile_ident);	\
	if (keyfile == NULL)						\
		return -ENXIO;						\
	err = func(arg, keyfile);					\
	__connman_storage_close(profile_ident, keyfile, FALSE);		\
	return err;							\
} while (0)

#define	DOSAVE(func, arg, profile_ident) do {				\
	int err;							\
	GKeyFile *keyfile = __connman_storage_open(profile_ident);	\
	if (keyfile == NULL)						\
		return -ENXIO;						\
	err = func(arg, keyfile);					\
	/* NB: always write-back for compatibility */			\
	__connman_storage_close(profile_ident, keyfile, TRUE);		\
	return err;							\
} while (0)

int __connman_storage_load_service(struct connman_service *service,
    const struct connman_storage_ident *profile_ident)
{
	GSList *list;

	_DBG_STORAGE("service %p", service);

	for (list = storage_list; list; list = list->next) {
		struct connman_storage *storage = list->data;

		if (storage->service_load)
			DOLOAD(storage->service_load, service, profile_ident);
	}
	return -ENOENT;
}

int __connman_storage_save_service(struct connman_service *service,
    const struct connman_storage_ident *profile_ident)
{
	GSList *list;

	_DBG_STORAGE("service %p", service);

	for (list = storage_list; list; list = list->next) {
		struct connman_storage *storage = list->data;

		if (storage->service_save)
			DOSAVE(storage->service_save, service, profile_ident);
	}
	return -ENOENT;
}

char *__connman_storage_find_guid(const char *guid,
    const struct connman_storage_ident *profile_ident)
{
	char *identifier = NULL;
	GKeyFile *keyfile;

	_DBG_STORAGE("guid %s profile %p", guid, profile_ident);

	keyfile = __connman_storage_open(profile_ident);
	if (keyfile != NULL) {
		gsize ngroups;
		gchar **groups = g_key_file_get_groups(keyfile, &ngroups);
		int i;

		for (i = 0; i < ngroups && identifier == NULL; i++) {
			char *str = g_key_file_get_string(keyfile, groups[i],
			    "GUID", NULL);
			if (g_strcmp0(guid, str) == 0)
				identifier = g_strdup(groups[i]);
			g_free(str);
		}
		g_strfreev(groups);
		__connman_storage_close(profile_ident, keyfile, FALSE);
	}
	return identifier;
}

int __connman_storage_load_provider(struct connman_provider *provider,
    const struct connman_storage_ident *profile_ident)
{
	GSList *list;

	_DBG_STORAGE("provider %p", provider);

	for (list = storage_list; list; list = list->next) {
		struct connman_storage *storage = list->data;

		if (storage->provider_load)
			DOLOAD(storage->provider_load, provider, profile_ident);
	}
	return -ENOENT;
}

int __connman_storage_save_provider(struct connman_provider *provider,
    const struct connman_storage_ident *profile_ident)
{
	GSList *list;

	_DBG_STORAGE("provider %p", provider);

	for (list = storage_list; list; list = list->next) {
		struct connman_storage *storage = list->data;

		if (storage->provider_save)
			DOSAVE(storage->provider_save, provider, profile_ident);
	}
	return -ENOENT;
}

int __connman_storage_load_device(struct connman_device *device,
    const struct connman_storage_ident *profile_ident)
{
	GSList *list;

	_DBG_STORAGE("device %p", device);

	for (list = storage_list; list; list = list->next) {
		struct connman_storage *storage = list->data;

		if (storage->device_load)
			DOLOAD(storage->device_load, device, profile_ident);
	}
	return -ENOENT;
}

int __connman_storage_save_device(struct connman_device *device,
    const struct connman_storage_ident *profile_ident)
{
	GSList *list;

	_DBG_STORAGE("device %p", device);

	for (list = storage_list; list; list = list->next) {
		struct connman_storage *storage = list->data;

		if (storage->device_save)
			DOSAVE(storage->device_save, device, profile_ident);
	}
	return -ENOENT;
}

int __connman_storage_load_ipconfig(struct connman_ipconfig *ipconfig,
    const struct connman_storage_ident *profile_ident)
{
	GSList *list;

	_DBG_STORAGE("ipconfig %p", ipconfig);

	for (list = storage_list; list; list = list->next) {
		struct connman_storage *storage = list->data;

		if (storage->ipconfig_load)
			DOLOAD(storage->ipconfig_load, ipconfig, profile_ident);
	}
	return -ENOENT;
}

int __connman_storage_save_ipconfig(const struct connman_ipconfig *ipconfig,
    const struct connman_storage_ident *profile_ident)
{
	GSList *list;

	_DBG_STORAGE("ipconfig %p", ipconfig);

	for (list = storage_list; list; list = list->next) {
		struct connman_storage *storage = list->data;

		if (storage->ipconfig_save)
			DOSAVE(storage->ipconfig_save, ipconfig, profile_ident);
	}
	return -ENOENT;
}

int __connman_storage_init(void)
{
	_DBG_STORAGE("");

	return 0;
}

void __connman_storage_cleanup(void)
{
	_DBG_STORAGE("");
}
